﻿using CNative.Cloud.CPlatform;
using CNative.Cloud.CPlatform.Convertibles;
using CNative.Cloud.CPlatform.Runtime.Client;
using CNative.Cloud.ProxyGenerator.Implementation;
using ImpromptuInterface;
using System;
using System.Collections.Generic;
using System.Collections.Concurrent;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using System.Linq;
using CNative.Cloud.CPlatform.Utilities;

namespace CNative.Cloud.ProxyGenerator
{
    /// <summary>
    /// RPC通用客户端代码类
    /// </summary>
    public sealed class CommonClientProxy : ServiceProxyBase
    {
        private readonly CPlatform.Ids.IServiceIdGenerator _serviceIdGenerator;
        private readonly ConcurrentDictionary<string, Tuple<string, Type, ParameterInfo[], Func<Type, IDictionary<string, object>, string, dynamic>>> dicMethod 
            = new ConcurrentDictionary<string, Tuple<string, Type, ParameterInfo[], Func<Type, IDictionary<string, object>, string, dynamic>>>();
       public Type ServiceType { get; set; }

        public CommonClientProxy(CPlatform.Ids.IServiceIdGenerator serviceIdGenerator, IRemoteInvokeService remoteInvokeService
            , ITypeConvertibleService typeConvertibleService, String serviceKey, CPlatformContainer serviceProvider)
            : base(remoteInvokeService, typeConvertibleService, serviceKey, serviceProvider)
        {
            _serviceIdGenerator = serviceIdGenerator;
        }

        public static IN Wrap<IN>(CPlatform.Ids.IServiceIdGenerator serviceIdGenerator, IRemoteInvokeService remoteInvokeService
            , ITypeConvertibleService typeConvertibleService, String serviceKey, CPlatformContainer serviceProvider) where IN : class
        {
            return new CommonClientProxy(serviceIdGenerator, remoteInvokeService, typeConvertibleService, serviceKey, serviceProvider)
            { ServiceType = typeof(IN) }
            .ActLike<IN>();
        }
        public static dynamic Wrap(Type serviceType, CPlatform.Ids.IServiceIdGenerator serviceIdGenerator, IRemoteInvokeService remoteInvokeService
            , ITypeConvertibleService typeConvertibleService, String serviceKey, CPlatformContainer serviceProvider)
        {
            return new CommonClientProxy(serviceIdGenerator, remoteInvokeService, typeConvertibleService, serviceKey, serviceProvider)
            { ServiceType = serviceType }
            .ActLike(serviceType);
        }


        public override bool TryInvokeMember(System.Dynamic.InvokeMemberBinder binder, object[] args, out object result)
        {
            try
            {
                result = null;

                Type[] argsTypes = GetTypeArray(args, out bool argExistsNull);
                var mid = GenerateMethodId(binder.Name, argsTypes);
                if (dicMethod.TryGetValue(mid, out Tuple<string, Type, ParameterInfo[], Func<Type, IDictionary<string, object>, string, dynamic>> tp))
                {
                    var parameters = CreateParameters(tp.Item3, args);
                    result = tp.Item4(tp.Item2, parameters, tp.Item1);
                }
                else
                {
                    var method = GetMethodInfo(ServiceType, binder.Name, argsTypes, argExistsNull);
                    if (method == null)
                    {
                        throw new Exception("TryInvokeMember method == null");
                    }
                    var returnType = method.ReturnType.IsGenericType
                         ? method.ReturnType.GetGenericTypeDefinition()
                         : method.ReturnType;
                    var serviceId = _serviceIdGenerator.GenerateServiceId(method);

                    var parameters = CreateParameters(method.GetParameters(), args);

                    Func<Type, IDictionary<string, object>, string, dynamic> func = null;
                    if (returnType == typeof(Task))
                    {
                        func = ((_returnType, _parameters, _serviceId)
                            => Invoke(_parameters, _serviceId));
                    }
                    else if (returnType == typeof(Task<>))
                    {
                        var arguments = method.ReturnType.GetGenericArguments();
                        var invokeReturnInstance = InvokeReturnMethod.MakeGenericMethod(arguments);

                        func = ((_returnType, _parameters, _serviceId)
                             => invokeReturnInstance.Invoke(this, new object[] { _parameters, _serviceId }));
                    }
                    else if (returnType == typeof(void))
                    {
                        func = ((_returnType, _parameters, _serviceId)
                            => { Invoke(_parameters, _serviceId)?.Wait(); return null; });
                    }
                    else
                    {
                        func = ((_returnType, _parameters, _serviceId)
                          => Invoke(_returnType, _parameters, _serviceId)?.Result);
                    }
                    dicMethod[mid] = new Tuple<string, Type, ParameterInfo[], Func<Type, IDictionary<string, object>, string, dynamic>>
                        (serviceId, returnType, method.GetParameters(), func);

                    result = func(returnType, parameters, serviceId);
                }

                return true;
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }

        readonly MethodInfo InvokeReturnMethod = typeof(CommonClientProxy).GetMethod("InvokeReturn",BindingFlags.NonPublic | BindingFlags.Instance);
        internal Task<T> InvokeReturn<T>(IDictionary<string, object> parameters, string serviceId)
        {
            return Invoke<T>(parameters, serviceId);
        }

        private IDictionary<string, object> CreateParameters(System.Reflection.ParameterInfo[] parameters, object[] args)
        {
            var dicParameters = new Dictionary<string, object>();
            if (parameters?.Length > 0)
            {
                for (var i = 0; i < parameters.Length; i++)
                {
                    var parameter = parameters[i];
                    if (args?.Length >= i)
                    {
                        dicParameters[parameter.Name] = args[i];
                    }
                }
            }
            return dicParameters;
        }

        private Type[] GetTypeArray(object[] paramters, out bool argExistsNull)
        {
            argExistsNull = false;
            Type[] argsTypes = new Type[0];
            try
            {
                if (paramters?.Length > 0)
                {
                    argsTypes = new Type[paramters.Length]; //Type.GetTypeArray(paramters);
                    for (var i = 0; i < paramters.Length; i++)
                    {
                        if (paramters[i] != null)
                        {
                            argsTypes[i] = paramters[i].GetType();
                        }
                        else
                        {
                            argsTypes[i] = null;
                            argExistsNull = true;
                        }
                    }
                }
            }
            catch
            {
                argsTypes = new Type[0];
            }
            return argsTypes;
        }
        private MethodInfo GetMethodInfo(Type _Type, string _sMethodName, Type[] argsTypes, bool argExistsNull = false)
        {
            int _iParamCount = argsTypes == null ? 0 : argsTypes.Length;
            var bindingAttr = BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public;
            if (argExistsNull)//如果入参存在空值
            {
                //通过名称获取所有配置的方法
                var _listMethodInfo = _Type.GetMethods(bindingAttr).ToList().FindAll(p => p.Name.Equals(_sMethodName));
                if (_listMethodInfo?.Count == 1)//唯一直接返回
                {
                    return _listMethodInfo[0];
                }
                else if (_listMethodInfo?.Count > 1)
                {
                    var _listMethodInfoc = _listMethodInfo.FindAll(p => p.GetParameters().Length.Equals(_iParamCount));
                    if (_listMethodInfoc?.Count == 1)//通过入参数量来配置方法，唯一直接返回
                    {
                        return _listMethodInfoc[0];
                    }
                    else if (_listMethodInfoc?.Count > 1)
                    {
                        //寻找参数类型匹配最多的方法
                        var mathCount = new List<int>(new int[_listMethodInfoc.Count]);
                        for (var i = 0; i < _listMethodInfoc.Count; i++)
                        {
                            var prs = _listMethodInfoc[i].GetParameters();
                            mathCount[i] = 0;
                            for (var k = 0; k < prs.Length; k++)
                            {
                                if ((argsTypes[k] != null 
                                        && (argsTypes[k] == prs[k].ParameterType || argsTypes[k] == Nullable.GetUnderlyingType(prs[k].ParameterType))) 
                                    || (argsTypes[k] == null 
                                        && (!prs[k].ParameterType.IsValueType || prs[k].ParameterType.IsNullable_())))
                                    mathCount[i]++;
                            }
                        }
                        return _listMethodInfoc[mathCount.IndexOf(mathCount.Max())];
                    }
                }
                return null;
            }
            //------------------------------------------------------------------------------------------------------------------
            if (_iParamCount > 0)
            {
                return _Type.GetMethod(_sMethodName, bindingAttr,//筛选条件
                         Type.DefaultBinder,//绑定
                         argsTypes,//参数类型
                         new ParameterModifier[] { new ParameterModifier(_iParamCount) }//参数个数
                          );
            }
            else
            {
                try
                {
                    return _Type.GetMethod(_sMethodName, bindingAttr);
                }
                catch (AmbiguousMatchException)
                {
                    return _Type.GetMethods(bindingAttr)?.FirstOrDefault(m => m.Name == _sMethodName && m.GetParameters()?.Length == 0);
                }
            }
        }

        private string GenerateMethodId(string _sMethodName, Type[] argsTypes)
        {
            if (_sMethodName == null)
                throw new ArgumentNullException(nameof(_sMethodName));

            var id = _sMethodName;

            if (argsTypes == null || argsTypes.Length==0)
                return _sMethodName;

            if (argsTypes.Any())
            {
                id += "_" + string.Join("_", argsTypes.Select(i => i == null ? "N" : i.Name));
            }
            return id;
        }

    }
}
